﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using GameUtils.Input;
using GameUtils;
using GameControls.GeneralObjects;

namespace GameControls
{
    public abstract class ControlBase : IDisposable
    {
        #region Properties
        #region Parent
        /// <summary>
        /// Parent property.
        /// </summary>
        internal ControlBase Parent { get; set; }
        #endregion

        #region IsComponent
        /// <summary>
        /// IsComponent property.
        /// </summary>
        internal bool IsComponent { get; set; }
        #endregion

        #region TransformedRect
        /// <summary>
        /// TransformedRect property.
        /// </summary>
        public RectF TransformedRect
        {
            get { return GetTransformedRectangleRelativeToAncestor(null); }
        }
        #endregion

        #region Rect
        private RectF mRect;

        /// <summary>
        /// Rect property.
        /// </summary>
        internal RectF Rect
        {
            get { return mRect; }
        }
        #endregion

        #region ChildrenOffset
        private Vector2F mChildrenOffset;

        /// <summary>
        /// ChildrenOffset property.
        /// </summary>
        protected Vector2F ChildrenOffset
        {
            get { return mChildrenOffset; }
        }
        #endregion

        #region ChildrenAndComponents
        private List<ControlBase> mChildrenAndComponents;

        /// <summary>
        /// ChildrenAndComponents property.
        /// </summary>
        private List<ControlBase> ChildrenAndComponents
        {
            get
            {
                if (mChildrenAndComponents == null)
                {
                    mChildrenAndComponents = new List<ControlBase>();
                    mChildrenAndComponents.AddRange(mChildren);
                    mChildrenAndComponents.AddRange(mComponents);
                }
                return mChildrenAndComponents;
            }
        }
        #endregion

        #region ComponentsAndChildren
        private List<ControlBase> mComponentsAndChildren;

        /// <summary>
        /// ComponentsAndChildren property.
        /// </summary>
        private List<ControlBase> ComponentsAndChildren
        {
            get
            {
                if (mComponentsAndChildren == null)
                {
                    mComponentsAndChildren = new List<ControlBase>();
                    mComponentsAndChildren.AddRange(mComponents);
                    mComponentsAndChildren.AddRange(mChildren);
                }
                return mComponentsAndChildren;
            }
        }
        #endregion

        private List<ControlBase> mChildren;
        private List<ControlBase> mComponents;
        #endregion

        #region Constructors
        public ControlBase(RectF pRect)
        {
            Parent = null;
            IsComponent = false;
            mRect = pRect;
            mChildrenOffset = new Vector2F();

            mChildren = new List<ControlBase>();
            mComponents = new List<ControlBase>();
        }
        #endregion

        #region Overriden Methods
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public void Update(GameTime pGameTime)
        {
            List<ControlBase> componentsAndChildren = ComponentsAndChildren;
            foreach (ControlBase control in componentsAndChildren)
            {
                control.Update(pGameTime);
            }

            UpdateImpl(pGameTime);
        }

        public void Draw(IControlRenderer pControlRenderer)
        {
            pControlRenderer.Render(this);

            List<ControlBase> childrenAndComponents = ChildrenAndComponents;
            foreach (ControlBase control in childrenAndComponents)
            {
                control.Draw(pControlRenderer);
            }
        }

        #region Children
        public void AddChild(ControlBase pControl)
        {
            mChildren.Add(pControl);
            pControl.Parent = this;

            OnChildrenChanged();
        }

        public void RemoveChild(ControlBase pControl)
        {
            mChildren.Remove(pControl);
            pControl.Parent = null;

            OnChildrenChanged();
        }

        public void ClearChildren()
        {
            foreach (ControlBase control in mChildren)
            {
                control.Parent = null;
            }
            mChildren.Clear();

            OnChildrenChanged();
        }
        #endregion

        #region Keyboard
        public void OnKeyDown(KeyEventArgs pArgs)
        {
            List<ControlBase> componentsAndChildren = ComponentsAndChildren;
            foreach (ControlBase control in componentsAndChildren)
            {
                control.OnKeyDown(pArgs);
            }

            OnKeyDownImpl(pArgs);
        }

        public void OnKeyUp(KeyEventArgs pArgs)
        {
            List<ControlBase> componentsAndChildren = ComponentsAndChildren;
            foreach (ControlBase control in componentsAndChildren)
            {
                control.OnKeyUp(pArgs);
            }

            OnKeyUpImpl(pArgs);
        }

        public void OnChar(CharacterEventArgs pArgs)
        {
            List<ControlBase> componentsAndChildren = ComponentsAndChildren;
            foreach (ControlBase control in componentsAndChildren)
            {
                control.OnChar(pArgs);
            }

            OnCharImpl(pArgs);
        }
        #endregion

        #region Mouse
        public void OnLeftButtonDown(MouseEventArgs pArgs)
        {
            List<ControlBase> componentsAndChildren = ComponentsAndChildren;
            foreach (ControlBase control in componentsAndChildren)
            {
                control.OnLeftButtonDown(pArgs);
            }

            OnLeftButtonDownImpl(pArgs);
        }

        public void OnLeftButtonUp(MouseEventArgs pArgs)
        {
            List<ControlBase> componentsAndChildren = ComponentsAndChildren;
            foreach (ControlBase control in componentsAndChildren)
            {
                control.OnLeftButtonUp(pArgs);
            }

            OnLeftButtonUpImpl(pArgs);
        }

        public void OnMiddleButtonDown(MouseEventArgs pArgs)
        {
            List<ControlBase> componentsAndChildren = ComponentsAndChildren;
            foreach (ControlBase control in componentsAndChildren)
            {
                control.OnMiddleButtonDown(pArgs);
            }

            OnMiddleButtonDownImpl(pArgs);
        }

        public void OnMiddleButtonUp(MouseEventArgs pArgs)
        {
            List<ControlBase> componentsAndChildren = ComponentsAndChildren;
            foreach (ControlBase control in componentsAndChildren)
            {
                control.OnMiddleButtonUp(pArgs);
            }

            OnMiddleButtonUpImpl(pArgs);
        }

        public void OnRightButtonDown(MouseEventArgs pArgs)
        {
            List<ControlBase> componentsAndChildren = ComponentsAndChildren;
            foreach (ControlBase control in componentsAndChildren)
            {
                control.OnRightButtonDown(pArgs);
            }

            OnRightButtonDownImpl(pArgs);
        }

        public void OnRightButtonUp(MouseEventArgs pArgs)
        {
            List<ControlBase> componentsAndChildren = ComponentsAndChildren;
            foreach (ControlBase control in componentsAndChildren)
            {
                control.OnRightButtonUp(pArgs);
            }

            OnRightButtonUpImpl(pArgs);
        }

        public void OnMouseMove(MouseEventArgs pArgs)
        {
            List<ControlBase> componentsAndChildren = ComponentsAndChildren;
            foreach (ControlBase control in componentsAndChildren)
            {
                control.OnMouseMove(pArgs);
            }

            OnMouseMoveImpl(pArgs);
        }

        public void OnMouseWheel(MouseEventArgs pArgs)
        {
            List<ControlBase> componentsAndChildren = ComponentsAndChildren;
            foreach (ControlBase control in componentsAndChildren)
            {
                control.OnMouseWheel(pArgs);
            }

            OnMouseWheelImpl(pArgs);
        }
        #endregion
        #endregion

        #region Methods
        protected virtual void Dispose(bool pDisposing)
        {
            if (pDisposing)
            {
                // free managed resources
            }
            
            // free native resources if there are any
        }

        protected virtual void UpdateImpl(GameTime pGameTime)
        {
        }

        #region Keyboard
        protected virtual void OnKeyDownImpl(KeyEventArgs pArgs)
        {
        }

        protected virtual void OnKeyUpImpl(KeyEventArgs pArgs)
        {
        }

        protected virtual void OnCharImpl(CharacterEventArgs pArgs)
        {
        }
        #endregion

        #region Mouse
        protected virtual void OnLeftButtonDownImpl(MouseEventArgs pArgs)
        {
        }

        protected virtual void OnLeftButtonUpImpl(MouseEventArgs pArgs)
        {
        }

        protected virtual void OnMiddleButtonDownImpl(MouseEventArgs pArgs)
        {
        }

        protected virtual void OnMiddleButtonUpImpl(MouseEventArgs pArgs)
        {
        }

        protected virtual void OnRightButtonDownImpl(MouseEventArgs pArgs)
        {
        }

        protected virtual void OnRightButtonUpImpl(MouseEventArgs pArgs)
        {
        }

        protected virtual void OnMouseMoveImpl(MouseEventArgs pArgs)
        {
        }

        protected virtual void OnMouseWheelImpl(MouseEventArgs pArgs)
        {
        }
        #endregion

        #region Children And Components
        protected void AddComponent(ControlBase pComponent)
        {
            mComponents.Add(pComponent);
            pComponent.Parent = this;
            pComponent.IsComponent = true;

            OnComponentsChanged();
        }

        protected void RemoveComponent(ControlBase pComponent)
        {
            mComponents.Remove(pComponent);
            pComponent.Parent = null;
            pComponent.IsComponent = false;

            OnComponentsChanged();
        }

        protected virtual void OnChildrenChanged()
        {
            mChildrenAndComponents = null;
            mComponentsAndChildren = null;
        }

        protected virtual void OnComponentsChanged()
        {
            mChildrenAndComponents = null;
            mComponentsAndChildren = null;
        }
        #endregion

        protected RectF GetTransformedRectangleRelativeToAncestor(ControlBase pAncestor)
        {
            RectF relativeTransformedRectangle;
            if (Parent != pAncestor && Parent != null)
            {
                RectF parentTranformedRect = Parent.GetTransformedRectangleRelativeToAncestor(pAncestor);
                Vector2F parentChildrenOffset = IsComponent ? Vector2F.Zero : Parent.ChildrenOffset;
                relativeTransformedRectangle = new RectF(
                    parentTranformedRect.X + parentChildrenOffset.X + Rect.X,
                    parentTranformedRect.Y + parentChildrenOffset.Y + Rect.Y,
                    Rect.Width,
                    Rect.Height);
            }
            else
            {
                relativeTransformedRectangle = Rect;
            }

            return relativeTransformedRectangle;
        }

        protected RectF GetContentBoundingRectangle()
        {
            float minX = 0.0f;
            float minY = 0.0f;
            float maxX = Rect.Width;
            float maxY = Rect.Height;

            foreach (ControlBase control in mChildren)
            {
                RectF childRect = control.Rect;
                minX = Math.Min(minX, childRect.X);
                minY = Math.Min(minY, childRect.Y);
                maxX = Math.Max(maxX, childRect.Right);
                maxY = Math.Max(maxY, childRect.Bottom);
            }

            return new RectF(minX, minY, maxX - minX, maxY - minY);
        }

        public RectF? GetClippingRect()
        {
            RectF? clippingRect;
            if (Parent != null)
            {
                RectF? parentClippingRectangle = Parent.GetClippingRect();
                if (parentClippingRectangle.HasValue)
                {
                    clippingRect = RectF.Intersect(Parent.TransformedRect, parentClippingRectangle.Value);
                }
                else
                {
                    clippingRect = Parent.TransformedRect;
                }
            }
            else
            {
                clippingRect = null;
            }

            return clippingRect;
        }

        internal virtual bool IsMouseOver(int pMouseX, int pMouseY)
        {
            RectF transformedRect = TransformedRect;
            bool isOverThis = transformedRect.Contains(pMouseX, pMouseY);
            if (Parent != null)
            {
                return isOverThis && Parent.IsMouseOver(pMouseX, pMouseY);
            }
            else
            {
                return isOverThis;
            }
        }

        #region Children Offset
        protected void SetChildrenOffset(int pX, int pY)
        {
            if (mChildrenOffset.X != pX || mChildrenOffset.Y != pY)
            {
                mChildrenOffset.X = pX;
                mChildrenOffset.Y = pY;
            }
        }

        protected void SetChildrenOffsetX(int pX)
        {
            if (mChildrenOffset.X != pX)
            {
                mChildrenOffset.X = pX;
            }
        }

        protected void SetChildrenOffsetY(int pY)
        {
            if (mChildrenOffset.Y != pY)
            {
                mChildrenOffset.Y = pY;
            }
        }

        protected void ChangeChildrenOffsetX(int pXChange)
        {
            if (pXChange != 0)
            {
                mChildrenOffset.X += pXChange;
            }
        }

        protected void ChangeChildrenOffsetY(int pYChange)
        {
            if (pYChange != 0)
            {
                mChildrenOffset.Y += pYChange;
            }
        }
        #endregion
        #endregion
    }
}